using System;
using System.Windows;
using System.Windows.Media;

namespace GraphMyCode.Controls
{
	public static class GeometryFactory
	{
		private const double ArrowHeight = 2;
		private const double ArrowWidth = 6;

		private static readonly double ThickCoef = Math.Sqrt(ArrowWidth * ArrowWidth + ArrowHeight * ArrowHeight) / (2 * ArrowHeight);

		public static Geometry CreateRect(double width)
		{
			return new RectangleGeometry { Rect = new Rect(0, 0, width, width) };
		}

		public static Geometry CreateCircle(double radius)
		{
			return new EllipseGeometry { RadiusX = radius, RadiusY = radius, Center = new Point(radius, radius) };
		}

		public static Geometry CreateArrow(Point fromPoint, Point toPoint, Geometry fromGeomertry, Geometry toGeometry, bool isDirectional,
		                                   double edgeThick)
		{
			var startPoint = new Point
			                 	{
			                 		X = toPoint.X >= fromPoint.X
			                 		    	? 0
			                 		    	: Math.Abs(toPoint.X - fromPoint.X),
			                 		Y = toPoint.Y >= fromPoint.Y
			                 		    	? 0
			                 		    	: Math.Abs(toPoint.Y - fromPoint.Y)
			                 	};

			var endPoint = new Point
			               	{
			               		X = toPoint.X <= fromPoint.X
			               		    	? 0
			               		    	: Math.Abs(toPoint.X - fromPoint.X),
			               		Y = toPoint.Y <= fromPoint.Y
			               		    	? 0
			               		    	: Math.Abs(toPoint.Y - fromPoint.Y)
			               	};

			Point iRelativeFromPoint = FindIntersection(endPoint, startPoint, fromGeomertry, edgeThick, false);

			Point iRelativePoint = FindIntersection(startPoint, endPoint, toGeometry, edgeThick, isDirectional);
		
			if (!isDirectional)
				return new LineGeometry { StartPoint = iRelativeFromPoint, EndPoint = iRelativePoint };

			double x1 = startPoint.X;
			double y1 = startPoint.Y;
			double x2 = iRelativePoint.X;
			double y2 = iRelativePoint.Y;

			double theta = Math.Atan2(y1 - y2, x1 - x2);
			double sint = Math.Sin(theta);
			double cost = Math.Cos(theta);

			var pt3 = new Point(
				x2 + (ArrowWidth * cost - ArrowHeight * sint),
				y2 + (ArrowWidth * sint + ArrowHeight * cost));

			var pt4 = new Point(
				x2 + (ArrowWidth * cost + ArrowHeight * sint),
				y2 - (ArrowHeight * cost - ArrowWidth * sint));

			var figures = new PathFigureCollection
			              	{
			              		new PathFigure
			              			{
			              				StartPoint = iRelativeFromPoint,
			              				Segments = new PathSegmentCollection
			              				           	{
			              				           		new LineSegment
			              				           			{
			              				           				Point =
			              				           					new Point(iRelativePoint.X + cost*ArrowWidth,
			              				           					          iRelativePoint.Y + sint*ArrowWidth)
			              				           			}
			              				           	}
			              			},
			              		new PathFigure
			              			{
			              				StartPoint = iRelativePoint,
			              				IsClosed = true,
			              				IsFilled = true,
			              				Segments =
			              					new PathSegmentCollection
			              						{new LineSegment {Point = pt3}, new LineSegment {Point = pt4}}
			              			}
			              	};


			return new PathGeometry { Figures = figures };
		}

		private static Point FindIntersection(Point fromPoint, Point toPoint, Geometry geometry, double edgeThick, bool isArrow)
		{
			double thickOffset = isArrow ? edgeThick*ThickCoef : 0;

			if (geometry is EllipseGeometry)
			{
				var e = (EllipseGeometry)geometry;

				double theta = Math.Atan2(fromPoint.Y - toPoint.Y, fromPoint.X - toPoint.X);
				double sint = Math.Sin(theta);
				double cost = Math.Cos(theta);

				return new Point(toPoint.X + (e.RadiusX + thickOffset) * cost,
				                 toPoint.Y + (e.RadiusY + thickOffset) * sint);
			}

			if (geometry is RectangleGeometry)
			{
				var r = (RectangleGeometry)geometry;


				if (toPoint.X == fromPoint.X)
					return toPoint.Y > fromPoint.Y
					       	? new Point(toPoint.X, toPoint.Y - thickOffset - r.Rect.Height / 2)
					       	: new Point(toPoint.X, toPoint.Y + thickOffset + r.Rect.Height / 2);

				double m = (toPoint.Y - fromPoint.Y) / (toPoint.X - fromPoint.X);
				int sign = fromPoint.X < toPoint.X ? 1 : -1;
				double theta = Math.Atan(m);

				double cost = Math.Cos(theta);
				double sint = Math.Sin(theta);

				Point retPoint;

				if (m < 0 && ((theta <= Math.PI / 4 && theta >= 0) || (theta >= -Math.PI / 4 && theta <= 0)))
				{
					m = Math.Abs(m);
					double y = toPoint.Y + sign * m * r.Rect.Width / 2;
					double x = toPoint.X - sign * r.Rect.Width / 2;
					retPoint = new Point(x, y);
				}

				else if (m < 0 && ((theta <= Math.PI / 2 && theta >= 0) || (theta >= -Math.PI / 2 && theta <= 0)))
				{
					m = Math.Abs(m);
					double y = toPoint.Y + sign * r.Rect.Width / 2;
					double x = toPoint.X - sign * r.Rect.Width / (m * 2);
					retPoint = new Point(x, y);
				}

				else if (((theta <= Math.PI / 4 && theta >= 0) || (theta >= -Math.PI / 4 && theta <= 0)))
				{
					double y = toPoint.Y - sign * m * r.Rect.Width / 2;
					double x = toPoint.X - sign * r.Rect.Width / 2;
					retPoint = new Point(x, y);
				}
				else
				{
					double y = toPoint.Y - sign * r.Rect.Width / 2;
					double x = toPoint.X - sign * r.Rect.Width / (m * 2);
					retPoint = new Point(x, y);
				}

				return new Point(retPoint.X - sign * cost * thickOffset, retPoint.Y - sign * sint * thickOffset);
			}

			return toPoint;
		}
	}
}